﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using nRoute.Components.Routing;
using nRoute.Navigation;
using nRoute.Navigation.Containers;
using nRoute.Components.Messaging;
using nRoute.Components;
using System.Collections.Generic;

namespace nRoute.Controls
{
    public partial class ApplicationFrameContainer
    {
        private const string CURRENT_PAGESTATE_INFO_KEY = "_ApplicationCurrentPageInfo";
        private const string BACK_STACK_KEY = "_ApplicationFrameBackStack";
        private const string FORWARD_STACK_KEY = "_ApplicationFrameForwardStack";
        private const string INTERMEDIATE_PAGESTATE_KEY = "_ApplicationFrameIntermediateState";

#region Overridable

        protected virtual bool OnFrameNavigating(NavigationMode navigationMode, Uri uri)
        {
            //var _cancelNavigation = ActiveNavigationRequest != null ? false : OnCancelPhoneNavigation();

            //if (!_cancelNavigation)
            //{
                if (navigationMode == NavigationMode.Back)
                {
                    if (_backStack.Count > 0)
                    {
                        // we get the request
                        var _backPageInfo = _backStack.Peek();
                        ActiveNavigationRequest = new NavigationRequest(_backPageInfo.Url, _backPageInfo.State, NavigateMode.Back);
                    }
                    else
                    {
                        return false;
                    }
                }
                else if (navigationMode == NavigationMode.Forward)
                {
                    if (_forwardStack.Count > 0)
                    {
                        // we get the request
                        var _forwardPageInfo = _forwardStack.Peek();
                        ActiveNavigationRequest = new NavigationRequest(_forwardPageInfo.Url, _forwardPageInfo.State, NavigateMode.Forward);
                    }
                    else
                    {
                        return false;
                    }
                }
                else if (navigationMode == NavigationMode.Refresh)
                {
                    if (CurrentNavigationRequest != null)
                    {
                        ActiveNavigationRequest = new NavigationRequest(CurrentNavigationRequest.RequestUrl,
                            CurrentNavigationRequest.RequestParameters, CurrentNavigationRequest.SiteArea, NavigateMode.Refresh);
                    }
                    else
                    {
                        return false;
                    }
                }

                // this basically allows us to redirect to the requested uri, whilst still providing nRoute navigation related functionality
                if (ActiveNavigationRequest == null)
                {
                    if (uri.OriginalString.Contains("app://external"))
                    {
                        ActiveNavigationRequest = new NavigationRequest(this.Url);
                    }
                    else
                    {
                        ActiveNavigationRequest = new NavigationRequest(String.Format(ABOUTFILE_FORMAT, uri.OriginalString));
                    }
                    if (CurrentNavigationRequest == null) CurrentNavigationRequest = ActiveNavigationRequest;
                    TransitionToNavigationState(Controls.NavigationState.Navigating);
                }

                // and save this
                CurrentNavigationMode = navigationMode;
            //}

            //return _cancelNavigation;
            return false;
        }

        protected virtual void OnFrameNavigated(Object content, Uri uri)
        {
            // we get the current page info
            var _currentPageInfo = GetCurrentPageContentState();
            var _nextPageInfo = default(PageContentState);

            if (ActiveNavigationResponse == null)
            {
                // this can happen - particularly when coming from outside of the application (like with the back button)
                if (ActiveNavigationRequest == null) OnFrameNavigating(NavigationMode.New, uri);

                ActiveNavigationResponse = new NavigationResponse(ActiveNavigationRequest, ResponseStatus.Success, 
                    content, ActiveNavigationRequest.RequestParameters);
            }

            // else as per the navigation mode we change the stacks
            switch (CurrentNavigationMode)
            {
                // we treat new and unknown alike                
                case NavigationMode.New:

                    // we check if we were to record
                    if (_currentPageInfo != null) _backStack.Push(_currentPageInfo);

                    // we also clear the forward stack
                    _forwardStack.Clear();

                    // and we say history changed
                    RaiseHistoryChanged();
                    break;

                case NavigationMode.Back:

                    // we push the current item into the forward stack
                    _forwardStack.Push(_currentPageInfo);

                    // we remove one item
                    _nextPageInfo = _backStack.Pop();

                    // and we say history changed
                    RaiseHistoryChanged();
                    break;

                case NavigationMode.Forward:

                    // we push the current item into the back stack
                    _backStack.Push(_currentPageInfo);

                    // and we remove one item
                    _nextPageInfo = _forwardStack.Pop();

                    // and we say history changed and return
                    RaiseHistoryChanged();
                    break;

                case NavigationMode.Refresh:

                    // we don't need to do anything here, as the history remains the same
                    _nextPageInfo = _currentPageInfo;
                    break;
            }

            // save and reset the active response 
            CurrentNavigationRequest = ActiveNavigationRequest;

            // get supporters
            CurrentNavigationLifecycleSupporter = Navigation.NavigationService.GetSupporter<ISupportNavigationLifecycle>(content);
            CurrentNavigationViewLifecycleSupporter = Navigation.NavigationService.GetSupporter<ISupportNavigationViewLifecycle>(content);
            CurrentNavigationStateSupporter = Navigation.NavigationService.GetSupporter<ISupportNavigationState>(content);
            CurrentNavigationViewStateSupporter = Navigation.NavigationService.GetSupporter<ISupportNavigationViewState>(content);

            // phone specific
            CurrentPhoneNavigationSupporter = Navigation.NavigationService.GetSupporter<ISupportPhoneNavigation>(content);
            CurrentPhoneVisualStateSupporter = Navigation.NavigationService.GetSupporter<ISupportPhoneVisualState>(content);

            // if the content support navigation, we pass in the request/response merged parameters
            if (this.CurrentNavigationLifecycleSupporter != null)
            {
                CurrentNavigationLifecycleSupporter.Initialize(ActiveNavigationResponse.ResponseParameters);
            }

            // this gets all the page related info
            this.PageInfo = Navigation.NavigationService.GetPageInfo(content as DependencyObject);

            // if the content supports state, and we are navigating back/forward
            if (CurrentNavigationStateSupporter != null)
            {
                // if we are navigating back  or forward, and we are to - also we treat the unknown state to be like a New navigation
                if (CurrentNavigationMode.GetValueOrDefault(NavigationMode.New) != NavigationMode.New)
                {
                    CurrentNavigationStateSupporter.RestoreState(_nextPageInfo != null ? _nextPageInfo.State : null);
                }
            }

            // if the content supports view state, and we are navigating back/forward
            if (CurrentNavigationViewStateSupporter != null)
            {
                // if we are navigating back  or forward, and we are to - also we treat the unknown state to be like a New navigation
                if (CurrentNavigationMode.GetValueOrDefault(NavigationMode.New) != NavigationMode.New)
                {
                    CurrentNavigationViewStateSupporter.RestoreState(_nextPageInfo != null ? _nextPageInfo.VisualState : null);
                }
            }

            // we raise completed
            OnNavigationCompleted(new NavigatedEventArgs(this, CurrentNavigationRequest));

            // and once we have set the transition state, then we call the view's initialize
            if (CurrentNavigationViewLifecycleSupporter != null) CurrentNavigationViewLifecycleSupporter.Initialize(ActiveNavigationResponse);

            // reset
            ActiveNavigationRequest = null;
            ActiveNavigationResponse = null;
            CurrentNavigationMode = null;
        }

        protected virtual void OnFrameNavigationFailed(Exception error)
        {
            ShowFailedNavigationState(ActiveNavigationRequest, ResponseStatus.Exception, error);

            // reset
            CurrentNavigationMode = null;
            ActiveNavigationRequest = null;
            ActiveNavigationResponse = null;
        }

        protected virtual void OnOrientationChanged(PageOrientation orientation)
        {
            if (this.CurrentPhoneVisualStateSupporter != null)
                this.CurrentPhoneVisualStateSupporter.OrientationChanged(orientation);
        }

        protected virtual bool OnCancelPhoneNavigation()
        {
            if (this.CurrentPhoneNavigationSupporter != null)
            {
                return this.CurrentPhoneNavigationSupporter.CancelPhoneNavigation();
            }
            return false;
        }

        protected virtual ParametersCollection OnSaveState()
        {
            var _parameters = new ParametersCollection();
            _parameters.Add(CURRENT_PAGESTATE_INFO_KEY, (PageContentState)CurrentNavigationRequest);

            if (_backStack.Count > 0 || _forwardStack.Count > 0)
            {
                // note we store an array, and not a stack - as we shouldn't store a reference
                _parameters.Add(BACK_STACK_KEY, _backStack.Reverse().ToArray());
                _parameters.Add(FORWARD_STACK_KEY, _forwardStack.Reverse().ToArray());
            }

            // NOTE AdiH: this intermediate state breaks tombstoning for nested containers. To be checked
            // if it's ok to get rid of it altogether or enhance it so tombstoning for nested containers works.
            //if (this.Url != null)
            //{
            //    _parameters.Add(INTERMEDIATE_PAGESTATE_KEY, GetCurrentPageContentState());
            //}

            return _parameters;
        }

        protected virtual void OnRestoreState(ParametersCollection state)
        {
            _backStack = new Stack<PageContentState>(state.GetValueOrDefault(BACK_STACK_KEY, Enumerable.Empty<PageContentState>()));
            _forwardStack = new Stack<PageContentState>(state.GetValueOrDefault(FORWARD_STACK_KEY, Enumerable.Empty<PageContentState>()));
            _intermediateState = state.GetValueOrDefault(INTERMEDIATE_PAGESTATE_KEY, default(PageContentState));
            RaiseHistoryChanged();

            var tombstonedPageInfo = _backStack.Pop();
            if (CurrentNavigationStateSupporter != null)
            {
                CurrentNavigationStateSupporter.RestoreState(tombstonedPageInfo != null ? tombstonedPageInfo.State : null);
            }
            if (CurrentNavigationViewStateSupporter != null)
            {
                CurrentNavigationViewStateSupporter.RestoreState(tombstonedPageInfo != null ? tombstonedPageInfo.VisualState : null);
            }

            //base.Content = null;            // remove the current content
            CurrentNavigationRequest = state.GetValueOrDefault<PageContentState>(CURRENT_PAGESTATE_INFO_KEY, default(PageContentState));
            //Refresh();                    // NOTE: We don't refresh
        }

#endregion

#region Overrides

        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            if (oldContent != null && oldContent is PhoneApplicationPage)
            {
                ((PhoneApplicationPage)oldContent).BackKeyPress -= ApplicationFrameContainer_BackKeyPress;
            }

            if (newContent != null && newContent is PhoneApplicationPage)
            {
                ((PhoneApplicationPage)newContent).BackKeyPress += ApplicationFrameContainer_BackKeyPress;
            }
        }

#endregion

#region Handlers

        private void ApplicationFrameContainer_BackKeyPress(object sender, CancelEventArgs e)
        {
            if (!e.Cancel)
            {
                e.Cancel = OnCancelPhoneNavigation();
            }
        }

        private void ApplicationFrameContainer_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
        {
            OnFrameNavigationFailed(e.Exception);
            e.Handled = true;
        }

        private void ApplicationFrameContainer_Navigated(object sender, NavigationEventArgs e)
        {
            OnFrameNavigated(e.Content, e.Uri);
        }

        private void ApplicationFrameContainer_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            e.Cancel = OnFrameNavigating(e.NavigationMode, e.Uri);
        }

        private void ApplicationFrameContainer_Unobscured(object sender, EventArgs e)
        {
            if (this.CurrentPhoneVisualStateSupporter != null)
                this.CurrentPhoneVisualStateSupporter.ObscurationChanged(ObscurationState.Unobscured);
            Channel<ObscurationState>.Public.OnNext(ObscurationState.Unobscured);
        }

        private void ApplicationFrameContainer_Obscured(object sender, ObscuredEventArgs e)
        {
            if (this.CurrentPhoneVisualStateSupporter != null)
                this.CurrentPhoneVisualStateSupporter.ObscurationChanged(e.IsLocked ? ObscurationState.Locked : ObscurationState.Obscured);
            Channel<ObscurationState>.Public.OnNext(e.IsLocked ? ObscurationState.Locked : ObscurationState.Obscured);
        }

        private void ApplicationFrameContainer_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            OnOrientationChanged(e.Orientation);
            Channel<PageOrientation>.Public.OnNext(e.Orientation);
        }

#endregion

    }
}